Skip to content

fix: gracefully close streamable HTTP sessions on shutdown#2716

Open
Hackerchen716 wants to merge 1 commit into
modelcontextprotocol:mainfrom
Hackerchen716:contrib/streamable-http-graceful-shutdown
Open

fix: gracefully close streamable HTTP sessions on shutdown#2716
Hackerchen716 wants to merge 1 commit into
modelcontextprotocol:mainfrom
Hackerchen716:contrib/streamable-http-graceful-shutdown

Conversation

@Hackerchen716
Copy link
Copy Markdown

Background

Fixes #2150.

StreamableHTTPSessionManager currently cancels its task group during lifespan shutdown before asking active stateful transports to close. StreamableHTTPServerTransport.terminate() also closes request/session streams, but it does not close active SSE stream writers.

Problem

When a server shuts down while clients have active Streamable HTTP SSE responses open, those response tasks can be cancelled before the response stream completes. That can surface as Uvicorn logging ASGI callable returned without completing response.

Solution

  • Make StreamableHTTPServerTransport.terminate() idempotent.
  • Close active SSE stream writers during terminate() so EventSourceResponse tasks can complete cleanly.
  • Have StreamableHTTPSessionManager terminate tracked active transports before cancelling its task group.
  • Keep shutdown cleanup resilient: a failure terminating one transport is logged and does not skip later active transports.

Validation

Ran:

UV_CACHE_DIR=/tmp/mcp-python-sdk-contrib-yaXdqD/.uv-cache XDG_CACHE_HOME=/tmp/mcp-python-sdk-contrib-yaXdqD/.cache uv run --frozen pytest tests/server/test_streamable_http_manager.py -q
UV_CACHE_DIR=/tmp/mcp-python-sdk-contrib-yaXdqD/.uv-cache XDG_CACHE_HOME=/tmp/mcp-python-sdk-contrib-yaXdqD/.cache uv run --frozen pytest tests/shared/test_streamable_http.py -q
UV_CACHE_DIR=/tmp/mcp-python-sdk-contrib-yaXdqD/.uv-cache XDG_CACHE_HOME=/tmp/mcp-python-sdk-contrib-yaXdqD/.cache uv run --frozen ruff check .
UV_CACHE_DIR=/tmp/mcp-python-sdk-contrib-yaXdqD/.uv-cache XDG_CACHE_HOME=/tmp/mcp-python-sdk-contrib-yaXdqD/.cache uv run --frozen ruff format --check .
UV_CACHE_DIR=/tmp/mcp-python-sdk-contrib-yaXdqD/.uv-cache XDG_CACHE_HOME=/tmp/mcp-python-sdk-contrib-yaXdqD/.cache uv run --frozen pyright
UV_CACHE_DIR=/tmp/mcp-python-sdk-contrib-yaXdqD/.uv-cache XDG_CACHE_HOME=/tmp/mcp-python-sdk-contrib-yaXdqD/.cache uv run --frozen coverage run -m pytest tests/server/test_streamable_http_manager.py -q
UV_CACHE_DIR=/tmp/mcp-python-sdk-contrib-yaXdqD/.uv-cache XDG_CACHE_HOME=/tmp/mcp-python-sdk-contrib-yaXdqD/.cache uv run --frozen coverage combine
UV_CACHE_DIR=/tmp/mcp-python-sdk-contrib-yaXdqD/.uv-cache XDG_CACHE_HOME=/tmp/mcp-python-sdk-contrib-yaXdqD/.cache uv run --frozen coverage report --include="src/mcp/server/streamable_http.py,src/mcp/server/streamable_http_manager.py,tests/server/test_streamable_http_manager.py" --fail-under=0
UV_CACHE_DIR=/tmp/mcp-python-sdk-contrib-yaXdqD/.uv-cache XDG_CACHE_HOME=/tmp/mcp-python-sdk-contrib-yaXdqD/.cache UV_FROZEN=1 uv run --frozen strict-no-cover

Results:

  • manager tests: 14 passed
  • shared Streamable HTTP tests: 61 passed
  • ruff, format, pyright, coverage, strict-no-cover: passed

Risk

Low. The behavior change is limited to shutdown/session termination paths. Normal request handling and stateless per-request cleanup are unchanged.

Breaking Changes

None.

Reviewer Notes

The main behavior to review is the ordering: active stateful transports are asked to terminate before the session manager cancels the task group, and terminate() now closes SSE stream writers in addition to request/session streams.

@Hackerchen716 Hackerchen716 force-pushed the contrib/streamable-http-graceful-shutdown branch from 6841fdc to 08d234d Compare May 30, 2026 05:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Active Streamable HTTP sessions are not terminated during shutdown

1 participant